package com.mspbots.web.security.service;

import com.mspbots.api.RestServerException;
import com.mspbots.web.BaseService;
import com.mspbots.web.core.service.TenantService;
import com.mspbots.web.reports.MessageReportsService;
import com.mspbots.web.security.OauthResult;
import com.mspbots.web.security.User;
import com.mspbots.web.security.UserOnly;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.BeanUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * com.mspbots.web.security.AuthService
 *
 * @author Alex bob(https://github.com/vnobo)
 * @date Created by 2019/7/19
 */
@Log4j2
@Service
@RequiredArgsConstructor
public class AuthService extends BaseService {

    public final static String DEFAULT_ADMIN_ROLE_NAME = "ROLE_ADMIN";
    public final static String DEFAULT_TENANT_ADMIN_ROLE_NAME = "ROLE_TENANT_ADMIN";
    public final static String DEFAULT_TENANT_USER_ROLE_NAME = "ROLE_TENANT_USER";

    private final UsersService usersService;
    private final TenantService tenantService;
    private final MessageReportsService messageReportsService;

    public Mono<OauthResult> oauth(String username) {
        return this.usersService.loadUser(username)
                .flatMap(user -> {
                    UserOnly userOnly = new UserOnly();
                    BeanUtils.copyProperties(user, userOnly);
                    OauthResult result = OauthResult.builder()
                            .identifier(user.getIdentifier()).user(userOnly).authorities(user.getAuthorities()).build();
                    return this.tenantService.loadById(user.getTenantId())
                            .map(tenant -> {
                                result.setTenant(tenant);
                                return result;
                            }).switchIfEmpty(Mono.just(result));
                });
    }

    public Mono<OauthResult> teamsLogin(String teamsUserId, String email, ServerWebExchange exchange) {
        return this.usersService.loadTeamsUser(email)
                .switchIfEmpty(Mono.defer(() -> Mono.error(new TeamsUserNotFoundException("This login teams user by email ["
                        + email + "] have been is null."))))
                .filter(User::getEnabled)
                .switchIfEmpty(Mono.defer(() -> Mono.error(new TeamsUserNotPermissionException("This login teams user by id ["
                        + teamsUserId + "] have been banned by the administrator."))))
                .doOnNext(user -> this.usersService.setLastLogin(user.getUsername())
                        .subscribe(res -> log.info("User {} login last time ok result{}", user.getUsername(), res)))
                .flatMap(user -> this.tenantService.loadById(user.getTenantId())
                        .switchIfEmpty(Mono.defer(() -> Mono.error(new TeamsUserNotFoundException("This login user tenant by id ["
                                + user.getTenantId() + "] have been is null."))))
                        .flatMap(tenant -> {
                            UserDetails userDetails = org.springframework.security.core.userdetails.User.withUsername(user.getUsername())
                                    .password(user.getPassword())
                                    .authorities(user.getAuthorities().toArray(new String[0]))
                                    .accountExpired(!user.getEnabled())
                                    .credentialsExpired(!user.getEnabled())
                                    .disabled(!user.getEnabled())
                                    .accountLocked(!user.getEnabled())
                                    .build();
                            SecurityContext context = new SecurityContextImpl(new UsernamePasswordAuthenticationToken(userDetails,
                                    userDetails.getPassword(), userDetails.getAuthorities()));
                            UserOnly userOnly = new UserOnly();
                            BeanUtils.copyProperties(user, userOnly);
                            return exchange.getSession()
                                    .doOnNext(session -> session.getAttributes().put("SPRING_SECURITY_CONTEXT", context))
                                    .map(session -> OauthResult.builder().token(session.getId())
                                            .identifier(user.getIdentifier())
                                            .tenant(tenant).user(userOnly).authorities(user.getAuthorities()).teamsId(teamsUserId).build());
                        })).doOnSuccess(user -> {
                    // notify reports
                    messageReportsService.loginsRecordMessage(user.getTenant().getId().longValue(), user.getUser().getUsername(), "");
                });


    }


    static class PasswordIncorrectException extends RestServerException {

        PasswordIncorrectException(String message) {
            this(500, message);
        }

        PasswordIncorrectException(int code, String msg) {
            super(code, msg);
        }
    }

    static class TeamsUserNotPermissionException extends RestServerException {

        TeamsUserNotPermissionException(String message) {
            this(504, message);
        }

        TeamsUserNotPermissionException(int code, String msg) {
            super(code, msg);
        }
    }

    static class TeamsUserNotFoundException extends RestServerException {

        TeamsUserNotFoundException(String message) {
            this(403, message);
        }

        TeamsUserNotFoundException(int code, String msg) {
            super(code, msg);
        }
    }

}
